/*
 *  Copyright 2011 jmarsden.
 * 
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 * 
 *       http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *  under the License.
 */
package cc.plural.jsonij.marshal;

import java.lang.reflect.Array;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import cc.plural.jsonij.JSON;
import cc.plural.jsonij.Value;

/**
 *
 * @author jmarsden
 */
public class JavaMarshaler {
    
    JavaObjectMarshaler javaObjectMarshaler;

    public JavaMarshaler() {
        javaObjectMarshaler = new JavaObjectMarshaler(this);
    }

    public Value marshalObject(Object o) {
        CycleDetector cycleDetector = new CycleDetector();
        return marshalAnyObject(o, cycleDetector);
    }

    public Value marshalObject(boolean[] a) {
        int size = 0;
        if (( size = Array.getLength(a) ) == 0) {
            return new JSON.Array<JSON.Numeric>();
        }
        JSON.Array<JSON.Boolean> marshaledArray = new JSON.Array<JSON.Boolean>();
        for (int i = 0; i < size; i++) {
            if (a[i]) {
                marshaledArray.add(JSON.TRUE);
            } else {
                marshaledArray.add(JSON.FALSE);
            }
        }
        return marshaledArray;
    }

    public Value marshalObject(Boolean[] a) {
        int size = 0;
        if (( size = Array.getLength(a) ) == 0) {
            return new JSON.Array<JSON.Numeric>();
        }
        JSON.Array<JSON.Boolean> marshaledArray = new JSON.Array<JSON.Boolean>();
        for (int i = 0; i < size; i++) {
            if (a[i]) {
                marshaledArray.add(JSON.TRUE);
            } else {
                marshaledArray.add(JSON.FALSE);
            }
        }
        return marshaledArray;
    }

    public Value marshalObject(int[] a) {
        int size = 0;
        if (( size = Array.getLength(a) ) == 0) {
            return new JSON.Array<JSON.Numeric>();
        }
        JSON.Array<JSON.Numeric> marshaledArray = new JSON.Array<JSON.Numeric>();
        for (int i = 0; i < size; i++) {
            marshaledArray.add(new JSON.Numeric(a[i]));
        }
        return marshaledArray;
    }

    public Value marshalObject(Integer[] a) {
        int size = 0;
        if (( size = Array.getLength(a) ) == 0) {
            return new JSON.Array<JSON.Numeric>();
        }
        JSON.Array<JSON.Numeric> marshaledArray = new JSON.Array<JSON.Numeric>();
        for (int i = 0; i < size; i++) {
            marshaledArray.add(new JSON.Numeric(a[i]));
        }
        return marshaledArray;
    }

    public Value marshalObject(char[] a) {
        int size = 0;
        if (( size = Array.getLength(a) ) == 0) {
            return new JSON.Array<JSON.Numeric>();
        }
        JSON.Array<JSON.String> marshaledArray = new JSON.Array<JSON.String>();
        for (int i = 0; i < size; i++) {
            marshaledArray.add(new JSON.String("" + a[i]));
        }
        return marshaledArray;
    }

    public Value marshalObject(Character[] a) {
        int size = 0;
        if (( size = Array.getLength(a) ) == 0) {
            return new JSON.Array<JSON.Numeric>();
        }
        JSON.Array<JSON.String> marshaledArray = new JSON.Array<JSON.String>();
        for (int i = 0; i < size; i++) {
            marshaledArray.add(new JSON.String("" + a[i]));
        }
        return marshaledArray;
    }

    public Value marshalObject(double[] a) {
        int size = 0;
        if (( size = Array.getLength(a) ) == 0) {
            return new JSON.Array<JSON.Numeric>();
        }
        JSON.Array<JSON.Numeric> marshaledArray = new JSON.Array<JSON.Numeric>();
        for (int i = 0; i < size; i++) {
            marshaledArray.add(new JSON.Numeric(a[i]));
        }
        return marshaledArray;
    }

    public Value marshalObject(Double[] a) {
        int size = 0;
        if (( size = Array.getLength(a) ) == 0) {
            return new JSON.Array<JSON.Numeric>();
        }
        JSON.Array<JSON.Numeric> marshaledArray = new JSON.Array<JSON.Numeric>();
        for (int i = 0; i < size; i++) {
            marshaledArray.add(new JSON.Numeric(a[i]));
        }
        return marshaledArray;
    }

    public Value marshalObject(float[] a) {
        int size = 0;
        if (( size = Array.getLength(a) ) == 0) {
            return new JSON.Array<JSON.Numeric>();
        }
        JSON.Array<JSON.Numeric> marshaledArray = new JSON.Array<JSON.Numeric>();
        for (int i = 0; i < size; i++) {
            marshaledArray.add(new JSON.Numeric(a[i]));
        }
        return marshaledArray;
    }

    public Value marshalObject(Float[] a) {
        int size = 0;
        if (( size = Array.getLength(a) ) == 0) {
            return new JSON.Array<JSON.Numeric>();
        }
        JSON.Array<JSON.Numeric> marshaledArray = new JSON.Array<JSON.Numeric>();
        for (int i = 0; i < size; i++) {
            marshaledArray.add(new JSON.Numeric(a[i]));
        }
        return marshaledArray;
    }

    public Value marshalObject(short[] a) {
        int size = 0;
        if (( size = Array.getLength(a) ) == 0) {
            return new JSON.Array<JSON.Numeric>();
        }
        JSON.Array<JSON.Numeric> marshaledArray = new JSON.Array<JSON.Numeric>();
        for (int i = 0; i < size; i++) {
            marshaledArray.add(new JSON.Numeric(a[i]));
        }
        return marshaledArray;
    }

    public Value marshalObject(Short[] a) {
        int size = 0;
        if (( size = Array.getLength(a) ) == 0) {
            return new JSON.Array<JSON.Numeric>();
        }
        JSON.Array<JSON.Numeric> marshaledArray = new JSON.Array<JSON.Numeric>();
        for (int i = 0; i < size; i++) {
            marshaledArray.add(new JSON.Numeric(a[i]));
        }
        return marshaledArray;
    }

    public Value marshalObject(long[] a) {
        int size = 0;
        if (( size = Array.getLength(a) ) == 0) {
            return new JSON.Array<JSON.Numeric>();
        }
        JSON.Array<JSON.Numeric> marshaledArray = new JSON.Array<JSON.Numeric>();
        for (int i = 0; i < size; i++) {
            marshaledArray.add(new JSON.Numeric(a[i]));
        }
        return marshaledArray;
    }

    public Value marshalObject(Long[] a) {
        int size = 0;
        if (( size = Array.getLength(a) ) == 0) {
            return new JSON.Array<JSON.Numeric>();
        }
        JSON.Array<JSON.Numeric> marshaledArray = new JSON.Array<JSON.Numeric>();
        for (int i = 0; i < size; i++) {
            marshaledArray.add(new JSON.Numeric(a[i]));
        }
        return marshaledArray;
    }

    public Value marshalObject(String[] a) {
        int size = 0;
        if (( size = Array.getLength(a) ) == 0) {
            return new JSON.Array<JSON.Numeric>();
        }
        JSON.Array<JSON.String> marshaledArray = new JSON.Array<JSON.String>();
        for (int i = 0; i < size; i++) {
            marshaledArray.add(new JSON.String(a[i]));
        }
        return marshaledArray;
    }

    public Value marshalObject(Object[] a) {
        int size = 0;
        if (( size = Array.getLength(a) ) == 0) {
            return new JSON.Array<JSON.Numeric>();
        }
        CycleDetector cycleDetector = new CycleDetector();
        JSON.Array<Value> marshaledArray = new JSON.Array<Value>();
        for (int i = 0; i < size; i++) {
            Value marshaledObject = marshalJavaObject(a[i], cycleDetector);
            if(marshaledObject != null) {
                marshaledArray.add(marshaledObject);
            } else {
                marshaledArray.add(JSON.NULL);
            }
        }
        return marshaledArray;
    }

    protected Value marshalAnyObject(Object o, CycleDetector cycleDetector) {
        if(o == null) {
            return JSON.NULL;
        }
        Value marshaledObject = null;
        Class<?> objectClass = o.getClass();
        JavaType objectType = JavaType.inspectObjectType(objectClass);
        switch (objectType) {
            case BOOLEAN:
                marshaledObject = marshalJavaBoolean(o);
                break;
            case BYTE:
            case INTEGER:
            case SHORT:
            case DOUBLE:
            case FLOAT:
            case LONG:
                marshaledObject = marshalJavaNumeric(o);
                break;
            case STRING:
                marshaledObject = marshalJavaString(o);
                break;
            case ENUM:
                marshaledObject = marshalJavaEnum(o);
                break;
            case ARRAY:
            case ARRAY_BYTE:
            case ARRAY_SHORT:
            case ARRAY_INTEGER:
            case ARRAY_FLOAT:
            case ARRAY_DOUBLE:
            case ARRAY_LONG:
            case ARRAY_STRING:
            case ARRAY_ENUM:
            case ARRAY_LIST:
            case ARRAY_MAP:
            case ARRAY_ARRAY:
                marshaledObject = marshalJavaArray(o, cycleDetector);
                break;
            case LIST:
                marshaledObject = marshalJavaList(o, cycleDetector);
                break;
            case OBJECT:
                marshaledObject = marshalJavaObject(o, cycleDetector);
                break;
            case MAP:
                marshaledObject = marshalJavaMap(o, cycleDetector);
                break;
            case UNKOWN:
                marshaledObject = new JSON.String(o.getClass().getCanonicalName() + "@" + Integer.toHexString(o.hashCode()));
        }
        return marshaledObject;
    }

    protected Value marshalJavaBoolean(Object o) {
        Value value = null;
        boolean marshaledBoolean = (Boolean) o;
        if (marshaledBoolean) {
            value = JSON.TRUE;
        } else {
            value = JSON.FALSE;
        }
        return value;
    }

    protected Value marshalJavaNumeric(Object o) {
        Value value = null;
        Number marshaledNumber = null;
        marshaledNumber = (Number) o;
        if (marshaledNumber != null) {
            value = new JSON.Numeric(marshaledNumber);
        } else {
            value = JSON.NULL;
        }
        return value;
    }

    protected Value marshalJavaEnum(Object o) {
        Value value = null;
        if (o != null) {
            String marshaledEnumeration = o.toString();
            value = new JSON.String(marshaledEnumeration);
        } else {
            value = JSON.NULL;
        }
        return value;
    }

    protected Value marshalJavaString(Object o) {
        Value value = null;
        String marshaledString = (String) o;
        if (marshaledString != null) {
            value = new JSON.String(marshaledString);
        } else {
            value = JSON.NULL;
        }
        return value;
    }

    protected Value marshalJavaArray(Object o, CycleDetector cycleDetector) {
        Value value = null;
        if (o != null) {
            JSON.Array<Value> marshaledArray = new JSON.Array<Value>();
            int size = Array.getLength(o);
            Class<?> type = o.getClass();
            Class<?> componentType = type.getComponentType();
            if (componentType == int.class || componentType == Integer.class) {
                for (int i = 0; i < size; i++) {
                    marshaledArray.add(new JSON.Numeric(Array.getInt(o, i)));
                }
            } else if (componentType == double.class || componentType == Double.class) {
                for (int i = 0; i < size; i++) {
                    marshaledArray.add(new JSON.Numeric(Array.getDouble(o, i)));
                }
            } else if (componentType == float.class || componentType == Float.class) {
                for (int i = 0; i < size; i++) {
                    marshaledArray.add(new JSON.Numeric(Array.getFloat(o, i)));
                }
            } else if (componentType == short.class || componentType == Short.class) {
                for (int i = 0; i < size; i++) {
                    marshaledArray.add(new JSON.Numeric(Array.getShort(o, i)));
                }
            } else if (componentType == long.class || componentType == Long.class) {
                for (int i = 0; i < size; i++) {
                    marshaledArray.add(new JSON.Numeric(Array.getLong(o, i)));
                }
            } else {
                for (int i = 0; i < size; i++) {
                    Object arrayValue = Array.get(o, i);
                    marshaledArray.add(marshalAnyObject(arrayValue, cycleDetector.cloneCycleDetector()));
                }
            }
            value = marshaledArray;
        } else {
            value = JSON.NULL;
        }
        return value;
    }

    protected Value marshalJavaList(Object o, CycleDetector cycleDetector) {
        Value value = null;
        List<?> marshaledList = (List<?>) o;
        if (marshaledList != null) {
            JSON.Array<Value> marshaledArray = new JSON.Array<Value>();
            Iterator<?> marshaledListIterator = marshaledList.listIterator();
            Object listItem = null;
            while (marshaledListIterator.hasNext()) {
                listItem = marshaledListIterator.next();
                if (listItem == null) {
                    marshaledArray.add(JSON.NULL);
                    continue;
                }
                marshaledArray.add(marshalAnyObject(listItem, cycleDetector.cloneCycleDetector()));
            }
            value = marshaledArray;
        } else {
            value = JSON.NULL;
        }
        return value;
    }

    protected Value marshalJavaMap(Object o, CycleDetector cycleDetector) {
        Value value = null;
        Map<?, ?> marshaledMap = (Map<?, ?>) o;
        if (marshaledMap != null) {
            JSON.Object<JSON.String, Value> marshaledObject = new JSON.Object<JSON.String, Value>();
            Iterator<?> keySetIterator = marshaledMap.keySet().iterator();
            while (keySetIterator.hasNext()) {
                Object keyObject = keySetIterator.next();
                Object valueObject = marshaledMap.get(keyObject);
                marshaledObject.put(new JSON.String(keyObject.toString()), marshalAnyObject(valueObject, cycleDetector.cloneCycleDetector()));
            }
            value = marshaledObject;
        } else {
            value = JSON.NULL;
        }
        return value;
    }

    protected Value marshalJavaObject(Object o, CycleDetector cycleDetector) {
        Value marshaledValue = javaObjectMarshaler.marshalJavaObject(o, cycleDetector);
        return marshaledValue;
    }

    public static class CycleDetector implements Cloneable {

        final HashMap<Integer, Integer> list;

        public CycleDetector() {
            list = new HashMap<Integer, Integer>();
        }

        protected CycleDetector(HashMap<Integer, Integer> list) {
            this.list = list;
        }

        public boolean hashDetected(int hash) {
            return list.containsKey(hash);
        }

        public int getHashCount(int hash) {
            if(list.containsKey(hash)) {
               return list.get(hash);
            }
            return 0;
        }

        public void addHash(int hash) {
            if(!hashDetected(hash)) {
                list.put(hash, 0);
            } else {
                Integer value = list.remove(hash);
                list.put(hash, ++value);
            }
        }

        protected CycleDetector cloneCycleDetector() {
            HashMap<Integer, Integer> newHashMap = new HashMap<Integer,Integer>();
            Iterator<Integer> hashSetIterator = list.keySet().iterator();
            while(hashSetIterator.hasNext()) {
                Integer key = hashSetIterator.next();
                Integer value = list.get(key);
                newHashMap.put(key, value);
            }
            return new CycleDetector(newHashMap);
        }

        @Override
        protected Object clone() throws CloneNotSupportedException {
            return new CycleDetector((HashMap<Integer, Integer>) list.clone());
        }
    }
}
